// Copyright (c) 2025 Hemashushu <hippospark@gmail.com>, All rights reserved.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License version 2.0 and additional exceptions.
// For more details, see the LICENSE, LICENSE.additional, and CONTRIBUTING files.

use crate::object_file::ObjectFile;

/// Context for a running process.
pub struct Context<'a> {
    pub bytes: &'a [u8], // The UTF-8 byte stream of the source text.

    // Sub-routines.
    //
    // A routine corresponds to a route.
    // A sub-route will start a new routine, so
    // it is necessary to record sub-routines using a stack.
    pub routines: Vec<Routine>,

    // Pre-allocated capture group slots.
    pub match_ranges: Vec<MatchRange>,

    // Stack to store repetition counts.
    //
    // Repetition transitions may be nested.
    //
    // For example:
    // `(ab\d+){2,}` means
    // `at_least(("ab", one_or_more(char_digit)), 2)`.
    //
    // It is necessary to store the current repetition count
    // before entering a new transition.
    // This stack is used by `Transition::CounterSave` and `Transition::CounterInc`.
    //
    // The following diagram illustrates a complete repetition transition:
    //
    // ```diagram
    //                             repetition transition
    //                   /---------------------------------------\
    //                   |                                       |
    //                   |   | counter              | counter    |
    //                   |   | save                 | restore &  |
    //                   |   | transition           | increment  |
    //   in              v   v       /-----------\  v transition |
    //  ==o==-------=====o==--------==o in  out o==-------==o|o==/     out
    //        ^ counter  left        \-----------/     right |o==----==o==
    //        | reset                                             ^
    //        | transition                          counter check |
    //                                              transition    |
    // ```
    pub counter_stack: Vec<usize>,
}

/// Represents a routine generated by a route during process execution.
///
/// Each route generates one routine (similar to a "big" function in programming languages).
/// A process may have multiple routines since an object file can contain multiple routes.
/// At any given time, only one routine is executing. For example,
/// when a process enters another routine, the current routine is blocked
/// until the new routine completes.
pub struct Routine {
    pub start_position: usize, // Start position (inclusive, in bytes).
    pub end_position: usize,   // End position (exclusive, in bytes).
    pub route_index: usize,    // Index of the corresponding route.

    // A route consists of multiple nodes.
    // When a routine is running, it traverses nodes one by one based on transitions.
    //
    // Each node contains one or more transitions (similar to functions in programming languages).
    // These transitions are pushed onto this stack.
    //
    // When a transition is executed, it is popped from the stack:
    // - On failure: The next transition is popped and tried. If the stack is empty,
    //   it indicates that the route matching has failed.
    // - On success: The routine moves to the next node pointed to by the transition
    //   and pushes all transitions of the new node onto the stack. If the node is the
    //   last node of the route, it means the route matching has succeeded.
    pub transition_stack: Vec<TransitionStackItem>,
}

impl Routine {
    pub fn new(start_position: usize, end_position: usize, route_index: usize) -> Routine {
        Routine {
            start_position,
            end_position,
            route_index,
            transition_stack: vec![],
        }
    }
}

pub struct TransitionStackItem {
    pub position: usize,           // Current position (in bytes).
    pub repetition_count: usize,   // Repetition count for backtracking.
    pub current_node_index: usize, // Index of the node holding the transitions.
    pub transition_index: usize,   // Index of the target transition.
}

impl TransitionStackItem {
    pub fn new(
        position: usize,
        repetition_count: usize,
        current_node_index: usize,
        transition_index: usize,
    ) -> Self {
        TransitionStackItem {
            position,
            repetition_count,
            current_node_index,
            transition_index,
        }
    }
}

/// Represents the text range of capture group.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct MatchRange {
    pub start: usize, // Start position (inclusive).
    pub end: usize,   // End position (exclusive).
}

impl MatchRange {
    pub fn new(start: usize, end: usize) -> Self {
        MatchRange { start, end }
    }
}

impl<'a> Context<'a> {
    pub fn new(
        s: &'a str,
        number_of_capture_groups: usize, // Number of pre-allocated match slots.
    ) -> Self {
        let bytes = s.as_bytes();
        Self::from_bytes(bytes, number_of_capture_groups)
    }

    pub fn from_bytes(
        bytes: &'a [u8],
        number_of_capture_groups: usize, // Number of pre-allocated match slots.
    ) -> Self {
        Context {
            bytes,
            routines: vec![],
            counter_stack: vec![],

            // Allocate the vector of 'match ranges' for the capture groups.
            match_ranges: vec![MatchRange::default(); number_of_capture_groups],
        }
    }

    pub fn push_transitions_of_node(
        &mut self,
        object_file: &ObjectFile,
        node_index: usize,
        position: usize,
        repetition_count: usize,
    ) {
        let transition_count = {
            let routine = self.get_current_routine_ref();
            let route_index = routine.route_index;
            let node = &object_file.routes[route_index].nodes[node_index];
            node.transition_items.len()
        };

        // Add the indices of transitions in reverse order,
        // since the stack pops the last element first.
        let routine = self.get_current_routine_ref_mut();
        for transition_index in (0..transition_count).rev() {
            routine.transition_stack.push(TransitionStackItem::new(
                position,
                repetition_count,
                node_index,
                transition_index,
            ));
        }
    }

    pub fn pop_transition_stack_item(&mut self) -> Option<TransitionStackItem> {
        self.get_current_routine_ref_mut().transition_stack.pop()
    }

    #[inline]
    pub fn get_current_routine_ref(&self) -> &Routine {
        self.routines.last().unwrap()
    }

    #[inline]
    pub fn get_current_routine_ref_mut(&mut self) -> &mut Routine {
        let count = self.routines.len();
        &mut self.routines[count - 1]
    }
}
